#include <stx/imp/cmn.hpp>

inline namespace stx
{
std::optional<HttpRequestLine>
parseHttpRequestLine(std::string_view const sv_http_request_line)
{
    auto const mr =
        getMatchResults(sv_http_request_line,
                        rgx<R"(^(GET|POST)\s+(\S+)\s+HTTP/1\.1[\r]*$)">());
    if (mr.size() != 3)
    {
        LOG_ERROR("Invalid http request line: "sv,
                  mr.size(),
                  _I_,
                  sv_http_request_line);
        return {};
    }

    return HttpRequestLine{.method = "GET"sv == mr[1] ? HttpMethod::K_GET
                                                      : HttpMethod::K_POST,
                           .uri    = tos(mr[2])};
}

std::optional<HttpStatusLine>
parseHttpStatusLine(std::string_view const sv_http_status_line)
{
    auto const mr = getMatchResults(
        sv_http_status_line,
        rgx<R"(^HTTP/1\.1\s+([1-6][0-9][0-9])\s+(.*)[\r]*$)">());
    if (mr.size() != 3)
    {
        LOG_ERROR("Invalid http status line: "sv,
                  mr.size(),
                  _I_,
                  sv_http_status_line);
        return {};
    }

    auto opt_status = ton(mr[1]);
    if (!opt_status)
    {
        LOG_ERROR("Invalid http status line: "sv, sv_http_status_line);
        return {};
    }

    return HttpStatusLine{.status = *opt_status, .reason = tos(mr[2])};
}

class HttpMsgParser__ final
{
    enum class WholeParsingState_
    {
        K_EXPECTING_CR_1,
        K_EXPECTING_LF_1,
        K_EXPECTING_CR_2,
        K_EXPECTING_LF_2,
        K_READING_BODY,
    };

    enum class BodyParsingState_
    {
        K_EXPECTING_SIZE_CHAR,
        K_EXPECTING_SIZE_LF,
        K_READING_CHUNK,
        K_EXPECTING_CHUNK_CR,
        K_EXPECTING_CHUNK_LF,
        K_EXPECTING_END_CR,
        K_EXPECTING_END_LF,
    };

    WholeParsingState_ whole_parsing_state_ =
        WholeParsingState_::K_EXPECTING_CR_1;
    BodyParsingState_ body_parsing_state_ =
        BodyParsingState_::K_EXPECTING_SIZE_CHAR;
    std::deque<Buffer> tmp_bufs_;
    std::size_t        done_size_ = 0;
    HttpMsg            msg_;
    std::string        tmp_str_;
    bool               is_chunked_ = {};
    FsmState           fsm_state_  = FsmState::K_ACTIVE;

public:
    HttpMsgParser__() noexcept = default;

    [[nodiscard]] FsmState parse(char const c);
    [[nodiscard]] FsmState parse(MemView const mv);
    [[nodiscard]] HttpMsg  fetch();
    [[nodiscard]] FsmState getFsmState() const noexcept;

private:
    [[nodiscard]] FsmState parseChunkedBody_(char const c);
};

HttpMsgParser::HttpMsgParser() : imp_(HttpMsgParser__{})
{}

FsmState HttpMsgParser::parse(char const c)
{
    return std::any_cast<HttpMsgParser__&>(this->imp_).parse(c);
}

FsmState HttpMsgParser::parse(MemView const mv)
{
    return std::any_cast<HttpMsgParser__&>(this->imp_).parse(mv);
}

HttpMsg HttpMsgParser::fetch()
{
    return std::any_cast<HttpMsgParser__&>(this->imp_).fetch();
}

FsmState HttpMsgParser::getFsmState() const
{
    return std::any_cast<HttpMsgParser__ const&>(this->imp_).getFsmState();
}

/*
Some http servers use LF instead of CRLF as the header line break. Although not
conforming to the HTTP protocol, we had better recognize and accept that for
more compatibility.

A recipient MUST be able to parse the chunked transfer coding (Section 4.1) 
because it plays a crucial role in framing messages when the payload body size 
is not known in advance. A sender MUST NOT apply chunked more than once to a
message body (i.e., chunking an already chunked message is not allowed). If any 
transfer coding other than chunked is applied to a request payload body, the 
sender MUST apply chunked as the final transfer coding to ensure that the 
message is properly framed. If any transfer coding other than chunked is applied
to a response payload body, the sender MUST either apply chunked as the final 
transfer coding or terminate the message by closing the connection.

For example,

    Transfer-Encoding: gzip, chunked

indicates that the payload body has been compressed using the gzip coding and 
then chunked using the chunked coding while forming the message body.

The "identity" transfer coding token has been removed.

The Content-Length entity-header field indicates the size of the entity-body, in
decimal number of OCTETs, sent to the recipient or, in the case of the HEAD 
method, the size of the entity-body that would have been sent had the request 
been a GET.

HTTP/1.1 defines the "close" connection option for the sender to signal that the
connection will be closed after completion of the response.
*/
FsmState HttpMsgParser__::parse(char const c)
{
    auto const fn_reap_header = [this] -> bool
    {
        if (this->tmp_str_.empty())
        {
            LOG_ERROR("Header tmp string is empty."sv);
            return false;
        }

        auto const& re = rgx<R"(^([^:]+):\s*(.*)\s*$)">();
        auto        mr = getMatchResults(this->tmp_str_, re);
        if (mr.size() != 3)
        {
            LOG_ERROR("Header tmp string: "sv, this->tmp_str_);
            return false;
        }

        auto key   = tos(mr[1]);
        auto value = tos(mr[2]);

        if (iCaseEqual(key, "Transfer-Encoding"sv))
        {
            if (!iCaseEqual(value, "chunked"sv))
            {
                LOG_ERROR("Unsupported Transfer-Encoding: "sv, value);
                return false;
            }

            eraseAll(this->msg_.header_dict, "Content-Length"sv);
            this->is_chunked_ = true;
            this->tmp_bufs_.clear();
        }

        if (iCaseEqual(key, "Content-Length"sv))
        {
            if (this->is_chunked_)
            {
                // Ignore "Content-Length" if "Transfer-Encoding" is received.
                return true;
            }

            auto const opt = ton(value);
            if (!opt)
            {
                LOG_ERROR("Content-Length: "sv, value);
                return false;
            }

            insert(this->tmp_bufs_, Buffer(*opt));
        }

        this->msg_.header_dict[std::move(key)] = std::move(value);
        this->tmp_str_.clear();
        return true;
    };

    using enum WholeParsingState_;

    switch (this->whole_parsing_state_)
    {
    case K_EXPECTING_CR_1:
        if ('\r' == c || '\n' == c)
        {
            if (this->msg_.first_line.empty())
            {
                if (this->tmp_str_.empty())
                {
                    LOG_ERROR("The first line is empty."sv);
                    return this->fsm_state_ = FsmState::K_FAILURE;
                }

                this->msg_.first_line = std::move(this->tmp_str_);
            }
            else if (!fn_reap_header())
            {
                LOG_ERROR("Failed to fn_reap_header()."sv);
                return this->fsm_state_ = FsmState::K_FAILURE;
            }

            if ('\n' == c)
            {
                this->whole_parsing_state_ = K_EXPECTING_CR_2;
                return FsmState::K_ACTIVE;
            }

            this->whole_parsing_state_ = K_EXPECTING_LF_1;
            return FsmState::K_ACTIVE;
        }
        else if (std::isprint(c))
        {
            insert(this->tmp_str_, c);
            return FsmState::K_ACTIVE;
        }

        goto error_handler;

    case K_EXPECTING_LF_1:
        if ('\n' == c)
        {
            this->whole_parsing_state_ = K_EXPECTING_CR_2;
            return FsmState::K_ACTIVE;
        }

        goto error_handler;

    case K_EXPECTING_CR_2:
        if (std::isprint(c))
        {
            insert(this->tmp_str_, c);
            this->whole_parsing_state_ = K_EXPECTING_CR_1;
            return FsmState::K_ACTIVE;
        }
        else if ('\r' == c)
        {
            this->whole_parsing_state_ = K_EXPECTING_LF_2;
            return FsmState::K_ACTIVE;
        }
        else if ('\n' == c)
        {
            this->whole_parsing_state_ = K_READING_BODY;
            this->tmp_str_.clear();
            return FsmState::K_ACTIVE;
        }

        goto error_handler;

    case K_EXPECTING_LF_2:
        if ('\n' == c)
        {
            if (this->tmp_bufs_.empty() && !this->is_chunked_)
            {
                return this->fsm_state_ = FsmState::K_SUCCESS;
            }

            if ((contains(this->msg_.header_dict, "Content-Length"sv) &&
                 query(this->msg_.header_dict, "Content-Length"sv).mapped() !=
                     "0"sv) ||
                contains(this->msg_.header_dict, "Transfer-Encoding"sv))
            {
                this->whole_parsing_state_ = K_READING_BODY;
                this->tmp_str_.clear();
                return FsmState::K_ACTIVE;
            }

            return this->fsm_state_ = FsmState::K_SUCCESS;
        }

        goto error_handler;

    case K_READING_BODY:
        if (this->is_chunked_)
        {
            return this->parseChunkedBody_(c);
        }

        if (done_size_ < this->tmp_bufs_.back().size())
        {
            this->tmp_bufs_.back()[done_size_] = static_cast<std::byte>(c);
            if (++done_size_ >= this->tmp_bufs_.back().size())
            {
                return this->fsm_state_ = FsmState::K_SUCCESS;
            }

            return FsmState::K_ACTIVE;
        }

        goto error_handler;

    default:
        goto error_handler;
    }

error_handler:
    LOG_ERROR(this->whole_parsing_state_, _I_, bin2hex(c));

    return this->fsm_state_ = FsmState::K_FAILURE;
}

FsmState HttpMsgParser__::parse(MemView const mv)
{
    for (auto const c : mv)
    {
        auto const parsing_satate = this->parse(static_cast<char>(c));
        if (FsmState::K_ACTIVE == parsing_satate)
        {
            continue;
        }

        return parsing_satate;
    }

    return FsmState::K_ACTIVE;
}

HttpMsg HttpMsgParser__::fetch()
{
    this->msg_.body = mcat2<Buffer>(this->tmp_bufs_);
    return std::move(this->msg_);
}

FsmState HttpMsgParser__::getFsmState() const noexcept
{
    return this->fsm_state_;
}

FsmState HttpMsgParser__::parseChunkedBody_(char const c)
{
    using enum BodyParsingState_;

    switch (this->body_parsing_state_)
    {
    case K_EXPECTING_SIZE_CHAR:
        if (isHexChar(c))
        {
            insert(this->tmp_str_, c);
            return FsmState::K_ACTIVE;
        }

        if ('\r' == c)
        {
            this->body_parsing_state_ = K_EXPECTING_SIZE_LF;
            return FsmState::K_ACTIVE;
        }

        goto error_handler;

    case K_EXPECTING_SIZE_LF:
        if ('\n' == c)
        {
            auto const opt_chunk_size = ton(this->tmp_str_, NumberBase::K_16);
            if (!opt_chunk_size)
            {
                LOG_ERROR("Invalid size number: "sv, this->tmp_str_);
                return this->fsm_state_ = FsmState::K_FAILURE;
            }

            this->tmp_str_.clear();

            if (0 == *opt_chunk_size)
            {
                this->body_parsing_state_ = K_EXPECTING_END_CR;
                return FsmState::K_ACTIVE;
            }

            insert(this->tmp_bufs_, Buffer(*opt_chunk_size));
            ASSERT(this->tmp_bufs_.back().size() == *opt_chunk_size);

            this->body_parsing_state_ = K_READING_CHUNK;
            this->done_size_          = 0;
            return FsmState::K_ACTIVE;
        }

        goto error_handler;

    case K_READING_CHUNK:
        ASSERT(!this->tmp_bufs_.empty());
        if (this->done_size_ >= this->tmp_bufs_.back().size())
        {
            LOG_ERROR("The chunk data is too large: "sv);
            return this->fsm_state_ = FsmState::K_FAILURE;
        }

        this->tmp_bufs_.back()[this->done_size_++] = static_cast<std::byte>(c);
        if (this->tmp_bufs_.back().size() == this->done_size_)
        {
            this->body_parsing_state_ = K_EXPECTING_CHUNK_CR;
        }
        return FsmState::K_ACTIVE;

    case K_EXPECTING_CHUNK_CR:
        if ('\r' == c)
        {
            this->body_parsing_state_ = K_EXPECTING_CHUNK_LF;
            return FsmState::K_ACTIVE;
        }

        if ('\n' == c)
        {
            this->body_parsing_state_ = K_EXPECTING_SIZE_CHAR;
            return FsmState::K_ACTIVE;
        }

        goto error_handler;

    case K_EXPECTING_CHUNK_LF:
        if ('\n' == c)
        {
            this->body_parsing_state_ = K_EXPECTING_SIZE_CHAR;
            return FsmState::K_ACTIVE;
        }

        goto error_handler;

    case K_EXPECTING_END_CR:
        if ('\r' == c)
        {
            this->body_parsing_state_ = K_EXPECTING_END_LF;
            return FsmState::K_ACTIVE;
        }

        if ('\n' == c)
        {
            this->body_parsing_state_ = K_EXPECTING_END_LF;
            return this->fsm_state_   = FsmState::K_SUCCESS;
        }

        goto error_handler;

    case K_EXPECTING_END_LF:
        if ('\n' == c)
        {
            return this->fsm_state_ = FsmState::K_SUCCESS;
        }

        goto error_handler;

    default:
        goto error_handler;
    }

error_handler:
    LOG_ERROR(this->body_parsing_state_, _I_, bin2hex(c));
    return this->fsm_state_ = FsmState::K_FAILURE;
}

/*
All 1xx (informational), 204 (no content), and 304 (not modified) responses MUST
NOT include a message-body. All other responses do include a message-body, 
although it MAY be of zero length.

The transfer-length of a message is the length of the message-body as it appears
in the message; that is, after any transfer-codings have been applied. When a 
message-body is included with a message, the transfer-length of that body is 
determined by one of the following (in order of precedence):

1. Any response message which "MUST NOT" include a message-body (such as the 
1xx, 204, and 304 responses and any response to a HEAD request) is always terminated by the first empty line after the header fields, regardless of the 
entity-header fields present in the message.

2. If a Transfer-Encoding header field (section 14.41) is present and has any 
value other than "identity", then the transfer-length is defined by use of the 
"chunked" transfer-coding (section 3.6), unless the message is terminated by 
closing the connection.

3. If a Content-Length header field (section 14.13) is present, its decimal 
value in OCTETs represents both the entity-length and the transfer-length. The 
Content-Length header field MUST NOT be sent if these two lengths are different 
(i.e., if a Transfer-Encoding header field is present). If a message is received
with both a Transfer-Encoding header field and a Content-Length header field, 
the latter MUST be ignored.

4. If the message uses the media type "multipart/byteranges", and the 
transfer-length is not otherwise specified, then this self- delimiting media 
type defines the transfer-length. This media type MUST NOT be used unless the 
sender knows that the recipient can parse it; the presence in a request of a 
Range header with multiple byte- range specifiers from a 1.1 client implies that
the client can parse multipart/byteranges responses.

    A range header might be forwarded by a 1.0 proxy that does not understand 
	multipart/byteranges; in this case the server MUST delimit the message using
	methods defined in items 1,3 or 5 of this section.

5. By the server closing the connection. (Closing the connection cannot be used 
to indicate the end of a request body, since that would leave no possibility for
the server to send back a response.)
*/
Future<HttpMsg> recvHttpMsg(TcpSocket&           tcp_skt,
                            Duration const       dur_timeout,
                            MaxHttpMsgSize const max_msg_size)
{
    auto buf       = Buffer(C_PAGE_SIZE);
    auto parser    = HttpMsgParser{};
    auto body_size = 0uz;

    while (true)
    {
        auto status = co_await tcp_skt.read(buf, dur_timeout);
        if (0 == status)
        {
            co_return HttpMsg{};
        }

        if (status < 0)
        {
            auto s_err_msg = scat("err_msg: Failed to read: "sv, status);
            LOG_ERROR(s_err_msg);
            co_return HttpMsg{.first_line = std::move(s_err_msg)};
        }

        buf.resize(Number(status));
        body_size += static_cast<std::size_t>(status);

        if (body_size > max_msg_size)
        {
            auto s_err_msg =
                scat("err_msg: body size is greater than max_msg_size: "sv,
                     body_size,
                     _I_,
                     max_msg_size);
            LOG_ERROR(s_err_msg);
            co_return HttpMsg{.first_line = std::move(s_err_msg)};
        }

        switch (parser.parse(buf))
        {
        case FsmState::K_ACTIVE:
        {
            buf.resize(C_PAGE_SIZE);
            continue;
        }
        case FsmState::K_SUCCESS:
        {
            auto http_msg = parser.fetch();
            if (http_msg.header_dict.contains("Expect"sv))
            {
                auto hdr_dict           = HttpHeaderDict{};
                hdr_dict["Connection"s] = "keep-alive"s;

                status = co_await sendHttpMsg(tcp_skt,
                                              "HTTP/1.1 100 Continue\r\n"s,
                                              hdr_dict,
                                              {},
                                              dur_timeout);
                if (status < 0)
                {
                    auto s_err_msg = "err_msg: Failed to send 100-continue."s;
                    LOG_ERROR(s_err_msg);
                    co_return HttpMsg{.first_line = std::move(s_err_msg)};
                }

                parser = HttpMsgParser{};
                buf.resize(C_PAGE_SIZE);
                continue;
            }

            co_return http_msg;
        }
        case FsmState::K_FAILURE:
        {
            auto s_err_msg = "err_msg: Failed to parse http msg."s;
            LOG_ERROR(s_err_msg);
            co_return HttpMsg{.first_line = std::move(s_err_msg)};
        }
        default:
            break;
        }
    }

    auto s_err_msg = "err_msg: Unknown error."s;
    LOG_ERROR(s_err_msg);
    co_return HttpMsg{.first_line = std::move(s_err_msg)};
}

Future<std::ssize_t> sendHttpMsg(TcpSocket&        tcp_skt,
                                 std::string const s_first_line,
                                 HttpHeaderDict    hdr_dict,
                                 MemView const     mv_body,
                                 Duration const    dur_timeout)
{
    static constinit auto const sv_colon              = ": "sv;
    static constinit auto const sv_separator_line     = "\r\n"sv;
    static constinit auto const sv_key_content_length = "Content-Length"sv;
    static constinit auto const sv_key_transfer_encoding =
        "Transfer-Encoding"sv;

    auto       mvs           = std::deque<MemView>{};
    auto const s_body_length = tos(mv_body.size());

    insert(mvs, s_first_line);

    for (auto const& [key, value] : hdr_dict)
    {
        if (iCaseEqual(key, sv_key_content_length))
        {
            continue;
        }

        if (iCaseEqual(key, sv_key_transfer_encoding))
        {
            continue;
        }

        insert(mvs, key);
        insert(mvs, sv_colon);
        insert(mvs, value);
        insert(mvs, sv_separator_line);
    }

    insert(mvs, sv_key_content_length);
    insert(mvs, sv_colon);
    insert(mvs, s_body_length);
    insert(mvs, sv_separator_line);
    insert(mvs, sv_separator_line);
    insert(mvs, mv_body);

    co_return co_await tcp_skt.write(mvs, dur_timeout);
}

Future<HttpResponse> sendHttpRequest(TcpSocket&             tcp_skt,
                                     HttpMethod const       method,
                                     std::string_view const sv_http_path,
                                     HttpHeaderDict         hdr_dict,
                                     MemView const          mv_body,
                                     Duration const         dur_timeout,
                                     MaxHttpMsgSize const   max_msg_size)
{
    auto rsp = HttpResponse{};

    if (!hdr_dict.contains("Host"sv))
    {
        hdr_dict["Host"s] = "0.0.0.0"s;
    }

    if (!hdr_dict.contains("Accept"sv))
    {
        hdr_dict["Accept"s] = "*/*"s;
    }

    if (!hdr_dict.contains("Connection"sv))
    {
        hdr_dict["Connection"s] = "close"s;
    }

    auto s_first_line = scat(HttpMethod::K_GET == method ? "GET "sv : "POST "sv,
                             sv_http_path.empty() ? "/"sv : sv_http_path,
                             " HTTP/1.1\r\n"sv);

    auto status = co_await sendHttpMsg(tcp_skt,
                                       std::move(s_first_line),
                                       std::move(hdr_dict),
                                       mv_body,
                                       dur_timeout);

    if (status < 0)
    {
        rsp.status_line.status = static_cast<Status>(status);
        rsp.status_line.reason =
            scat("Failed to send http request (1): "sv, rsp.status_line.status);
        LOG_ERROR(rsp.status_line);
        co_return rsp;
    }

    auto http_msg = co_await recvHttpMsg(tcp_skt, dur_timeout, max_msg_size);
    if (http_msg.first_line.empty())
    {
        rsp.status_line.status = C_PEER_CLOSED;
        co_return rsp;
    }

    if (http_msg.first_line.starts_with("err_msg:"sv))
    {
        rsp.status_line.status = C_RECV_HTTP_MSG_ERROR;
        rsp.status_line.reason = std::move(http_msg.first_line);
        LOG_ERROR(rsp.status_line);
        co_return rsp;
    }

    auto opt_status_line = parseHttpStatusLine(http_msg.first_line);
    if (!opt_status_line)
    {
        rsp.status_line.status = C_PARSE_HTTP_STATUS_LINE_ERROR;
        rsp.status_line.reason =
            scat("Failed to parseHttpStatusLine: "sv, http_msg.first_line);
        LOG_ERROR(rsp.status_line);
        co_return rsp;
    }

    rsp.status_line = std::move(*opt_status_line);
    rsp.header_dict = std::move(http_msg.header_dict);
    rsp.body        = std::move(http_msg.body);

    co_return rsp;
}

[[nodiscard]] Future<HttpResponse>
sendHttpRequest(std::string_view const sv_url,
                HttpMethod const       method,
                HttpHeaderDict         hdr_dict,
                MemView const          mv_body,
                Duration const         dur_timeout,
                MaxHttpMsgSize const   max_msg_size)
{
    auto rsp = HttpResponse{};

    auto const& re =
        rgx<R"(^([hH][Tt][Tt][Pp]://)?(([^/:]+)(:([0-9]{1,5}))*)(.*)$)">();

    auto const mr = getMatchResults(sv_url, re);
    if (mr.size() != 7)
    {
        rsp.status_line.status = C_INVALID_URL;
        rsp.status_line.reason = scat("Invalid url: "sv, sv_url);
        LOG_ERROR(rsp.status_line.reason);
        co_return rsp;
    }

    auto const sv_addr = mr[3];
    auto const sv_port = mr[5];
    auto const sv_path = mr[6];

    auto port = Port(80);
    if (!sv_port.empty())
    {
        auto opt_port = ton(sv_port);
        if (!opt_port || *opt_port > max<std::uint16_t>())
        {
            rsp.status_line.status = C_INVALID_PORT;
            rsp.status_line.reason = scat("Invalid port: "sv, sv_url);
            LOG_ERROR(rsp.status_line.reason);
            co_return rsp;
        }

        port = Port(*opt_port);
    }

    auto       s_addr       = tos(sv_addr);
    auto const sv_http_path = sv_path.empty() ? "/"sv : sv_path;

    if (!isIp4(s_addr) && !isIp6(s_addr))
    {
        auto ips = co_await DnsCache::instance().query(s_addr);
        if (ips.empty())
        {
            rsp.status_line.status = C_DNS_QUERY_ERROR;
            rsp.status_line.reason =
                scat("Failed to query dns: "sv, s_addr, _I_, sv_url);
            LOG_ERROR(rsp.status_line.reason);
            co_return rsp;
        }

        s_addr = ips[rand(0uz, ips.size() - 1)];
    }

    if (!isIp4(s_addr) && !isIp6(s_addr))
    {
        rsp.status_line.status = C_INVALID_IP_ADDR;
        rsp.status_line.reason =
            scat("Invalid ip_addr: "sv, s_addr, _I_, sv_url);
        LOG_ERROR(rsp.status_line.reason);
        co_return rsp;
    }

    auto tcp_skt = co_await startTcpClient(s_addr, port);
    if (!tcp_skt)
    {
        rsp.status_line.status = tcp_skt.fd();
        rsp.status_line.reason = scat("Failed to startTcpClient: "sv,
                                      rsp.status_line.status,
                                      _I_,
                                      s_addr,
                                      _I_,
                                      port,
                                      _I_,
                                      sv_url);
        LOG_ERROR(rsp.status_line.reason);
        co_return rsp;
    }

    if (Port(80) == port)
    {
        hdr_dict["Host"s] = tos(sv_addr);
    }
    else
    {
        hdr_dict["Host"s] = scat(sv_addr, ":"sv, port);
    }

    co_return co_await sendHttpRequest(tcp_skt,
                                       method,
                                       sv_http_path,
                                       std::move(hdr_dict),
                                       mv_body,
                                       dur_timeout,
                                       max_msg_size);
}

Future<std::ssize_t> sendHttpResponse(TcpSocket&             tcp_skt,
                                      HttpStatus const       status,
                                      std::string_view const sv_reason,
                                      HttpHeaderDict         hdr_dict,
                                      MemView const          mv_body,
                                      Duration const         dur_timeout)
{
    auto s_first_line =
        scat("HTTP/1.1 "sv, tos(status), " "sv, sv_reason, "\r\n"sv);
    co_return co_await sendHttpMsg(tcp_skt,
                                   std::move(s_first_line),
                                   std::move(hdr_dict),
                                   mv_body,
                                   dur_timeout);
}

Future<> onHttpNewConn__(TcpSocket            tcp_skt,
                         RemoteEndpoint const remote_ep,
                         LocalEndpoint const  local_ep,
                         FnOnRecvHttpMsg      fn_on_recv_http_msg,
                         Duration const       dur_timeout,
                         MaxHttpMsgSize const max_msg_size)
{
    auto const s_conn_desc = scat("http_conn:"sv, remote_ep, _I_, local_ep);
    tcp_skt.disableTimeWait();

    while (true)
    {
        auto http_msg =
            co_await recvHttpMsg(tcp_skt, dur_timeout, max_msg_size);
        if (http_msg.first_line.empty())
        {
            co_return;
        }

        if (http_msg.first_line.starts_with("err_msg:"sv))
        {
            LOG_ERROR("Bad http request: "sv,
                      http_msg.first_line,
                      _I_,
                      s_conn_desc,
                      _I_,
                      dur_timeout.ms.count(),
                      _I_,
                      max_msg_size);
            auto const status =
                co_await sendHttpResponse(tcp_skt,
                                          HttpStatus(400),
                                          "Bad Request"sv,
                                          {
                                              {"Connection"s, "close"s},
                                          },
                                          {},
                                          dur_timeout);
            if (status < 0)
            {
                LOG_ERROR("Failed to sendHttpResponse: "sv,
                          status,
                          _I_,
                          s_conn_desc,
                          _I_,
                          dur_timeout.ms.count(),
                          _I_,
                          max_msg_size);
            }

            co_return;
        }

        if (contains(http_msg.header_dict, "Expect"sv))
        {
            auto const status =
                co_await sendHttpResponse(tcp_skt,
                                          HttpStatus(100),
                                          "Continue"sv,
                                          {
                                              {"Connection"s, "Keep-Alive"s},
                                          },
                                          {},
                                          dur_timeout);
            if (status < 0)
            {
                LOG_ERROR("Failed to respond with 100 Continue: "sv,
                          status,
                          _I_,
                          s_conn_desc,
                          _I_,
                          dur_timeout.ms.count());
                co_return;
            }

            continue;
        }

        auto const qr         = query(http_msg.header_dict, "Connection"sv);
        auto const close_conn = qr && iCaseEqual(qr.mapped(), "close"sv);

        auto const conn_state = co_await fn_on_recv_http_msg(
            tcp_skt, std::move(http_msg), remote_ep, local_ep);
        if (FsmState::K_ACTIVE != conn_state)
        {
            if (FsmState::K_SUCCESS == conn_state)
            {
                LOG_TRACE("Succeeded to process http request: "sv,
                          conn_state,
                          _I_,
                          s_conn_desc);
            }
            else
            {
                LOG_ERROR("Failed to process http request: "sv,
                          conn_state,
                          _I_,
                          s_conn_desc);
            }

            co_return;
        }

        if (close_conn)
        {
            LOG_TRACE(
                "Force close http conn: "sv, conn_state, _I_, s_conn_desc);
            co_return;
        }

        LOG_TRACE("Continue to process next http request: "sv, s_conn_desc);
    }
}

Future<> startHttpServer(LocalEndpoint        local_ep,
                         FnOnRecvHttpMsg      fn_on_recv_http_msg,
                         TcpBacklogSize const backlog_size,
                         SbReusePort const    sb_reuse_port,
                         Duration const       dur_timeout,
                         MaxHttpMsgSize const max_msg_size)
{
    co_return co_await startTcpServer(std::move(local_ep),
                                      std::bind(onHttpNewConn__,
                                                _1,
                                                _2,
                                                _3,
                                                std::move(fn_on_recv_http_msg),
                                                dur_timeout,
                                                max_msg_size),
                                      backlog_size,
                                      sb_reuse_port);
}

Future<> startHttpServer(LocalEndpoint   local_ep,
                         FnOnRecvHttpMsg fn_on_recv_http_msg)
{
    co_return co_await startHttpServer(
        std::move(local_ep), std::move(fn_on_recv_http_msg), {}, {}, 30s, {});
}

Future<> startOobServer(Ip4 const ip4, FnOnRecvHttpMsg fn_on_recv_http_msg)
{
    auto opt_tcp_svr = std::optional<TcpServer>{};
    auto local_ep    = Endpoint{};

    for (auto port = 60000_u16; port <= 60100_u16; ++port)
    {
        local_ep     = Endpoint(ip4, Port(port));
        auto tcp_svr = TcpServer(local_ep, {}, SbReusePort(false));
        if (tcp_svr)
        {
            opt_tcp_svr = std::move(tcp_svr);
            break;
        }
    }

    if (!opt_tcp_svr)
    {
        LOG_ERROR("Failed to startOobServer."sv);
        std::exit(EXIT_FAILURE);
    }

    auto fn_std_handler = [MOVE(fn_on_recv_http_msg)](
                              TcpSocket&            tcp_skt,
                              HttpMsg               msg,
                              RemoteEndpoint const& remote_ep,
                              LocalEndpoint const& local_ep) -> Future<FsmState>
    {
        auto const fn_get_state = [] -> std::string
        {
            return scat("IsLoggerEnabled: "sv,
                        gLoggerIsEnabled__().load(),
                        "\nLogLevel: "sv,
                        gLogLevel().load(),
                        "\nTeeLog: "sv,
                        gCopyLogsToTerminal().load(),
                        "\n"sv);
        };

        auto const fn_bad_rsp = [&] -> Future<FsmState>
        {
            auto const ssize = co_await sendHttpResponse(tcp_skt,
                                                         HttpStatus(400),
                                                         "BadRequestLine"sv,
                                                         {},
                                                         msg.first_line,
                                                         5s);
            if (ssize <= 0)
            {
                LOG_ERROR("Failed to sendHttpResponse: "sv, ssize);
                co_return FsmState::K_FAILURE;
            }

            co_return FsmState::K_SUCCESS;
        };

        auto const fn_good_rsp = [&] -> Future<FsmState>
        {
            auto const ssize = co_await sendHttpResponse(
                tcp_skt, HttpStatus(200), "OK"sv, {}, fn_get_state(), 5s);
            if (ssize <= 0)
            {
                LOG_ERROR("Failed to sendHttpResponse: "sv, ssize);
                co_return FsmState::K_FAILURE;
            }

            co_return FsmState::K_SUCCESS;
        };

        auto const opt = parseHttpRequestLine(msg.first_line);
        if (!opt)
        {
            co_return co_await fn_bad_rsp();
        }

        if ("/"sv == opt->uri)
        {
            co_return co_await fn_good_rsp();
        }

        if ("/set/log-level/0"sv == opt->uri)
        {
            gLogLevel() = 0;
            co_return co_await fn_good_rsp();
        }

        if ("/set/log-level/1"sv == opt->uri)
        {
            gLogLevel() = 1;
            co_return co_await fn_good_rsp();
        }

        if ("/enable/tee-log"sv == opt->uri)
        {
            gCopyLogsToTerminal() = true;
            co_return co_await fn_good_rsp();
        }

        if ("/disable/tee-log"sv == opt->uri)
        {
            gCopyLogsToTerminal() = false;
            co_return co_await fn_good_rsp();
        }

        if ("/enable/logger"sv == opt->uri)
        {
            gLoggerIsEnabled__() = true;
            co_return co_await fn_good_rsp();
        }

        if ("/disable/logger"sv == opt->uri)
        {
            gLoggerIsEnabled__() = false;
            co_return co_await fn_good_rsp();
        }

        co_return co_await fn_on_recv_http_msg(
            tcp_skt, std::move(msg), remote_ep, local_ep);
    };

    auto fn_on_new_conn = std::bind(onHttpNewConn__,
                                    _1,
                                    _2,
                                    _3,
                                    std::move(fn_std_handler),
                                    30s,
                                    MaxHttpMsgSize{});

    while (opt_tcp_svr)
    {
        auto remote_ep   = Endpoint{};
        auto tcp_svr_skt = co_await opt_tcp_svr->accept(&remote_ep);
        if (tcp_svr_skt)
        {
            LOG_INFO(
                "Accept ctrl panel connection: "sv, remote_ep, _I_, local_ep);
            fn_on_new_conn(
                std::move(tcp_svr_skt), std::move(remote_ep), local_ep);
            continue;
        }

        LOG_ERROR("Failed to accept ctrl panel connection: "sv,
                  tcp_svr_skt.fd(),
                  _I_,
                  local_ep);
    }
}

Future<FsmState> onBadOobRequest__(TcpSocket& tcp_skt,
                                   HttpMsg    msg,
                                   RemoteEndpoint const&,
                                   LocalEndpoint const&)
{
    auto const ssize = co_await sendHttpResponse(
        tcp_skt, HttpStatus(400), "BadRequestLine"sv, {}, msg.first_line, 5s);
    if (ssize <= 0)
    {
        LOG_ERROR("Failed to sendHttpResponse: "sv, ssize);
        co_return FsmState::K_FAILURE;
    }

    co_return FsmState::K_SUCCESS;
}

Future<> startOobServer(Ip4 const ip4)
{
    co_return co_await startOobServer(ip4, onBadOobRequest__);
}

Future<> startOobServer()
{
    co_return co_await startOobServer("127.0.0.1"_ip4, onBadOobRequest__);
}

std::string tos(HttpHeaderDict const& hdr_dict)
{
    static constinit auto sv_comma = ":"sv;
    if (hdr_dict.empty())
    {
        return {};
    }

    auto svs = std::vector<std::string_view>{};
    svs.reserve(hdr_dict.size() * 4);

    for (auto const& [key, value] : hdr_dict)
    {
        insert(svs, key);
        insert(svs, sv_comma);
        insert(svs, value);
        insert(svs, _I_);
    }

    svs.pop_back();
    return mcat2<std::string>(svs);
}

std::string tos(HttpRequestLine const& req_line)
{
    return scat(req_line.method, _I_, req_line.uri);
}

std::string tos(HttpStatusLine const& status_line)
{
    return scat(status_line.status, _I_, status_line.reason);
}

std::string tos(HttpRequest const& req, HttpBodyDumpOption const opt)
{
    switch (opt)
    {
    case HttpBodyDumpOption::K_AS_OCTETS:
        return scat(req.req_line.method,
                    _I_,
                    req.req_line.uri,
                    _I_,
                    tos(req.header_dict),
                    _I_,
                    bin2hex(req.body));

    case HttpBodyDumpOption::K_AS_TEXT:
        return scat(req.req_line.method,
                    _I_,
                    req.req_line.uri,
                    _I_,
                    tos(req.header_dict),
                    _I_,
                    req.body.tosv());
    default:
        break;
    }

    return scat(
        req.req_line.method, _I_, req.req_line.uri, _I_, tos(req.header_dict));
}

std::string tos(HttpResponse const& rsp, HttpBodyDumpOption const opt)
{
    switch (opt)
    {
    case HttpBodyDumpOption::K_AS_OCTETS:
        return scat(rsp.status_line.status,
                    _I_,
                    rsp.status_line.reason,
                    _I_,
                    tos(rsp.header_dict),
                    _I_,
                    bin2hex(rsp.body));

    case HttpBodyDumpOption::K_AS_TEXT:
        return scat(rsp.status_line.status,
                    _I_,
                    rsp.status_line.reason,
                    _I_,
                    tos(rsp.header_dict),
                    _I_,
                    rsp.body.tosv());
    default:
        break;
    }

    return scat(rsp.status_line.status,
                _I_,
                rsp.status_line.reason,
                _I_,
                tos(rsp.header_dict));
}

std::string tos(HttpMsg const& http_msg, HttpBodyDumpOption const opt)
{
    switch (opt)
    {
    case HttpBodyDumpOption::K_AS_OCTETS:
        return scat(http_msg.first_line,
                    _I_,
                    tos(http_msg.header_dict),
                    _I_,
                    bin2hex(http_msg.body));

    case HttpBodyDumpOption::K_AS_TEXT:
        return scat(http_msg.first_line,
                    _I_,
                    tos(http_msg.header_dict),
                    _I_,
                    http_msg.body.tosv());
    default:
        break;
    }

    return scat(http_msg.first_line, _I_, tos(http_msg.header_dict));
}

std::string tos(CurlResult const& cr, HttpBodyDumpOption const opt)
{
    return scat(
        cr.status, _I_, mcat2<std::string>(cr.cerr), _I_, tos(cr.rsp, opt));
}

Future<CurlResult> curl(std::string_view const sv_url,
                        Duration const         dur_timeout,
                        HttpHeaderDict const&  hdrs,
                        MemView const          mv_req_body)
{
    auto s_cmd = R"(curl -i -s -X POST --data-binary @- )"s;
    s_cmd.reserve(C_PAGE_SIZE);

    for (auto const& [key, value] : hdrs)
    {
        // Always remove user-defined "Expect: xxx"
        if (iCaseEqual(key, "Expect"sv))
        {
            continue;
        }

        // Always let curl set "Content-Length: xxx" or use user-defined
        // "Transfer-Encoding: chunked" if present.
        if (iCaseEqual(key, "Content-Length"sv))
        {
            continue;
        }

        s_cmd += R"(-H ")"sv;
        s_cmd += key;
        s_cmd += R"(: )"sv;
        s_cmd += value;
        s_cmd += R"(" )"sv;
    }

    // Always disable "Expect: 100-continue".
    s_cmd += R"(-H "Expect:" )"sv;

    s_cmd += sv_url;
    LOG_TRACE(s_cmd, _I_, mv_req_body.size());

    auto cr   = CurlResult{};
    auto srr  = co_await shellRaw(s_cmd, dur_timeout, mv_req_body);
    cr.status = srr.status;
    cr.cerr   = std::move(srr.cerr);

    auto parser = HttpMsgParser{};
    for (auto const& chunk : srr.cout)
    {
        for (auto const c : chunk)
        {
            if (FsmState::K_FAILURE == parser.parse(static_cast<char>(c)))
            {
                ASSERT(FsmState::K_SUCCESS != parser.getFsmState());
                goto NextStep;
            }
        }
    }

NextStep:
    if (FsmState::K_SUCCESS != parser.getFsmState())
    {
        cr.rsp.status_line.status = C_INVALID_FSM_STATE;
        cr.rsp.status_line.reason = scat(
            "Failed to parse http msg: "sv, mv_req_body.size(), _I_, s_cmd);
        co_return cr;
    }

    auto [first_line, header_dict, body] = parser.fetch();
    auto opt_status_line                 = parseHttpStatusLine(first_line);
    if (!opt_status_line)
    {
        cr.rsp.status_line.status = C_INVALID_HTTP_STATUS_LINE;
        cr.rsp.status_line.reason = scat("Failed to parseHttpStatusLine: "sv,
                                         mv_req_body.size(),
                                         _I_,
                                         first_line,
                                         _I_,
                                         s_cmd);
        co_return cr;
    }

    cr.rsp.status_line = std::move(*opt_status_line);
    cr.rsp.header_dict = std::move(header_dict);
    cr.rsp.body        = std::move(body);

    co_return cr;
}
} // namespace stx